/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.moderatorplugin;

import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Point;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import java.io.File;
import java.io.FilenameFilter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.List;
import java.util.Random;


import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DropMode;
import javax.swing.GroupLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.LayoutStyle;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.TransferHandler;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import com.pearreview.ui.PearPanel;
import com.pearreview.ui.tree.*;
import com.pearreview.application.Messages;
import com.pearreview.util.XMLFileFilter;
import com.pearreview.util.review.Aspect;
import com.pearreview.util.review.Catalog;
import com.pearreview.util.review.io.CatalogReader;
import com.pearreview.util.review.io.CatalogWriter;
import com.pearreview.util.review.io.XMLCatalogReader;
import com.pearreview.util.review.io.XMLCatalogWriter;

public class CatalogManager extends PearPanel {
	private static final long serialVersionUID = 5746863105242023907L;
	
	private static final String XML_PREFIX = "PearReview";
	
	public static final String BACKUP_EXTENSION = ".bak";
	public static final String CATALOG_DIRECTORY = "catalogs";
	
	private JTree tree;
	private CatalogTreeModel treeModel;
	private RightControlPanel operationPanel;
	private JPopupMenu popupMenu;
	private MouseAdapter popupMouseAdapter = new PopupMouseAdapter();
	private TreeSelectionListener selectionListener = new SelectionListener();
	
	private File workspace;
	private File[] catalogFiles;
	
	/* actions */
	private Action deleteAction = new DeleteAction();
	private Action addAspectAction = new AddAspectAction();
	private Action addCategoryAction = new AddCategoryAction();
	private Action addCatalogAction = new AddCatalogAction();
	private Action importAction = new ImportAction();;
	private Action exportAction = new ExportAction();
	
	private Action applyAction = new SaveAction();
	
	public CatalogManager(File workspace) {
		this.workspace = workspace;
		
		/* initialize components */
		initComponents();
		
		readCatalogs();
		
		tree.setCellRenderer(new CatalogTreeCellRenderer());
		tree.setRowHeight(0);
		
		tree.addMouseListener(popupMouseAdapter);
		tree.getSelectionModel().addTreeSelectionListener(selectionListener);
		
		/* drag and drop */
		tree.getSelectionModel()
				.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		tree.setDragEnabled(true);
		tree.setDropMode(DropMode.USE_SELECTION);
		
		DropTargetAdapter adapter = new TreeDropTargetAdapter();
		
		tree.setDropTarget(new ExpandPathDropTarget(tree, 
				TransferHandler.MOVE, adapter) {
			private static final long serialVersionUID = -499885536636918349L;
			@Override
			public void dragOver(DropTargetDragEvent dtde) {
				super.dragOver(dtde);
				Point p = dtde.getLocation();
				TreePath targetPath = tree.getClosestPathForLocation(p.x, p.y);
				TreePath sourcePath = tree.getSelectionPath();
				
				if(!canMove(targetPath, sourcePath) && 
						!treeModel.canMoveNode(sourcePath, targetPath))
					dtde.acceptDrag(TransferHandler.NONE);
			}
		});
		
		/* select root */
		tree.setSelectionPath(new TreePath(tree.getModel().getRoot()));
	}
	
	private void initComponents() {
		popupMenu = new JPopupMenu();
		JScrollPane scrollPane = new JScrollPane();
		tree = new JTree();
		KeyStroke deleteKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 
				0);
		tree.getInputMap().put(deleteKeyStroke, "removeNodes");
		tree.getActionMap().put("removeNodes", deleteAction);
		tree.setBorder(new EmptyBorder(10, 10, 10, 10));
		tree.add(popupMenu);
		JSeparator separator = new JSeparator();
		separator.setOpaque(false);
		operationPanel = new RightControlPanel(applyAction,
				exportAction,
				importAction,
				addCatalogAction,
				addCategoryAction,
				addAspectAction,
				deleteAction);

		ToolTipManager.sharedInstance().registerComponent(tree);
		
		scrollPane.setViewportView(tree);

		separator.setOrientation(SwingConstants.VERTICAL);
		
		GroupLayout layout = new GroupLayout(this);
		setLayout(layout);
		layout.setHorizontalGroup(
			layout.createParallelGroup()
				.addGroup(layout.createSequentialGroup()
					.addContainerGap()
					.addComponent(scrollPane, 
							GroupLayout.DEFAULT_SIZE, 
							GroupLayout.DEFAULT_SIZE, 
							Short.MAX_VALUE)
					.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
					.addComponent(separator, 
							GroupLayout.PREFERRED_SIZE,
							GroupLayout.PREFERRED_SIZE,
							GroupLayout.PREFERRED_SIZE)
					.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
					.addComponent(operationPanel,
							GroupLayout.PREFERRED_SIZE,
							GroupLayout.PREFERRED_SIZE,
							GroupLayout.PREFERRED_SIZE))
		);
		layout.setVerticalGroup(
			layout.createParallelGroup()
				.addGroup(GroupLayout.Alignment.TRAILING, 
						layout.createSequentialGroup()
					.addContainerGap()
					.addGroup(layout.createParallelGroup(
							GroupLayout.Alignment.TRAILING)
						.addComponent(operationPanel, 
								GroupLayout.Alignment.LEADING, 
								GroupLayout.DEFAULT_SIZE, 
								GroupLayout.DEFAULT_SIZE, 
								Short.MAX_VALUE)
						.addComponent(scrollPane, 
								GroupLayout.Alignment.LEADING, 
								0, 
								0, 
								Short.MAX_VALUE)
						.addComponent(separator, 
								GroupLayout.Alignment.LEADING, 
								GroupLayout.DEFAULT_SIZE, 
								GroupLayout.DEFAULT_SIZE, 
								Short.MAX_VALUE))
					.addContainerGap())
		);
	}
	
	public void readCatalogs() {
		catalogFiles = getCatalogFiles(workspace);
		
		/* read catalogs */
		Catalog[] catalogs = new Catalog[catalogFiles.length];
		String[] catalogNames = new String[catalogFiles.length];
		for(int i = 0 ; i < catalogFiles.length ; i++) {
			try {
				catalogs[i] = getCatalog(catalogFiles[i]);
				catalogNames[i] = getCatalogName(catalogFiles[i]);
			} catch(Throwable e) {
				// TODO error handling
				e.printStackTrace();
				
				catalogFiles[i] = null;
				catalogs[i] = null;
				catalogNames[i] = null;
			}
		}
		
		treeModel = new CatalogTreeModel();
		treeModel.createEmptyCatalogRoot(getLocalizedString("root"));
		TreePath rootPath = new TreePath(treeModel.getRoot());
		
		for(int i = 0 ; i < catalogFiles.length ; i++) {
			if(catalogFiles[i] == null)
				continue;
			
			treeModel.addCatalogNode(rootPath, 
					catalogs[i], 
					catalogNames[i]);
		}
		
		tree.setModel(treeModel);
	}
	
	/**
	 * Gets catalog name
	 * <br><b>NOTE</b>: make sure, that the given file is a catalog
	 * @param file file of an xml file
	 * @return catalog name
	 */
	public static String getCatalogName(File file) {
		String fileName = file.getName();
		int pointPos = fileName.lastIndexOf(".");
		
		if(pointPos <= 0)
			pointPos = fileName.length();
		
		return fileName.substring(0, pointPos);
	}
	
	/**
	 * Gets catalog names
	 * <br><b>NOTE</b>: make sure, that the given file is a catalog
	 * @param files files of an xml files
	 * @return catalog names
	 */
	public static String[] getCatalogNames(File[] files) {
		String[] names = new String[files.length];
		
		for(int i = 0 ; i < names.length ; i++) {
			names[i] = getCatalogName(files[i]);
		}
		
		return names;
	}
	
	/**
	 * Reads all catalogs of given files
	 * @param files catalog files to read
	 * @return red catalogs
	 * @throws Exception if one of given files could not be read
	 */
	public static Catalog[] getCatalogs(File[] files) throws Exception {
		Catalog[] catalogs = new Catalog[files.length];
		for(int i = 0 ; i < catalogs.length ; i++) {
			catalogs[i] = getCatalog(files[i]);
		}
		
		return catalogs;
	}
	
	/**
	 * Reads catalog of given file
	 * @param file catalog file to read
	 * @return readed catalog
	 * @throws Exception
	 */
	public static Catalog getCatalog(File file) throws Exception {
		CatalogReader catalogReader = new XMLCatalogReader();
		Catalog catalog = catalogReader.read(file);
		return catalog;
	}
	
	/**
	 * Gets all xml files in the workspace
	 * @param workspace search xml files in this path
	 * @return all xml files in workspace or empty array if workspace is
	 *  not a directory
	 */
	public static File[] getCatalogFiles(File workspace) {
		if(!workspace.isDirectory())
			return new File[0];
		
		/* get xml files */
		FilenameFilter filter = new XMLFileFilter();
		return workspace.listFiles(filter);
	}
	
	public void saveCatalogs() throws Throwable {
		/* backup old catalogs */
		File[] backupFiles = new File[catalogFiles.length];
		for(int i = 0 ; i < catalogFiles.length ; i++) {
			if(catalogFiles[i] == null) {
				continue;
			}
			
			backupFiles[i] = new File(catalogFiles[i].getParent(), 
					catalogFiles[i].getName() + BACKUP_EXTENSION);
			
			catalogFiles[i].renameTo(backupFiles[i]);
		}
		
		/* write catalogs from model */
		CatalogWriter writer = new XMLCatalogWriter(XML_PREFIX);
		int count = treeModel.getCatalogCount();
		Catalog[] catalogs = treeModel.getCatalogs();
		String[] names = treeModel.getCatalogNames();
		File[] files = new File[count];
		if(count > 0) {
			try {
				workspace.mkdirs();
			} catch(Throwable th) {
				// ignore
			}
		}
		for(int i = 0 ; i < count ; i++) {
			files[i] = new File(workspace, names[i] + XMLFileFilter.EXTENSION);
			writer.write(catalogs[i], files[i]);
		}
		
		/* remove backups */
		for(int i = 0 ; i < backupFiles.length ; i++) {
			if(backupFiles[i] == null) {
				continue;
			}
			
			backupFiles[i].delete();
		}
	}
	
	/**
	 * Checks if path can be removed
	 * @param path path to check
	 * @return <code>true</code> if path can be removed, otherwise
	 * <code>false</code>
	 */
	private boolean canRemove(TreePath path) {
		if(path == null || path.getPathCount() <= 1)
			return false;
		
		Object node = path.getLastPathComponent();
		
		if(node instanceof CatalogRootTreeNode)
			return false;
				
		return true;
	}
	
	private boolean canMove(TreePath targetPath, TreePath sourcePath) {
		if(targetPath == null || sourcePath == null)
			return false;
		
		Object sourceObj = sourcePath.getLastPathComponent();
		Object targentObj = targetPath.getLastPathComponent();
		
		if(!(sourceObj instanceof TreeNode) 
				|| !(targentObj instanceof TreeNode)) {
			return false;
		}
		
		if(!canRemove(sourcePath))
			return false;
		
		TreeNode sourceNode = (TreeNode)sourceObj;
		
		if(sourceNode.getParent() == null)
			return false;
		
		if(sourcePath.isDescendant(targetPath))
			return false;
		if(sourceObj instanceof CategoryTreeNode 
				&& targentObj instanceof AspectTreeNode)
			return false;
		if(sourceObj instanceof AspectsRootTreeNode)
			return false;
		if(sourceObj instanceof CategoryTreeNode)
			return canInsertCategory(targetPath, null);
		if(sourceNode instanceof AspectTreeNode)
			return canInsertAspect(targetPath, null);
		
		return false;
	}
	
	private boolean canInsertAspect(TreePath path, String name) {
		if(path == null || path.getPathCount() <= 0)
			return false;
		
		Object node = path.getLastPathComponent();
		
		if(!(node instanceof CategoryTreeNode) 
				&& !(node instanceof CatalogTreeNode)
				&& !(node instanceof AspectsTreeNode)
				&& !(node instanceof CatalogTreeNode)
				&& !(node instanceof AspectTreeNode))
			return false;
		
		if(name == null)
			return true;
		
		TreeNode tn = (TreeNode) node;
		int count = tn.getChildCount();
		
		for(int i = 0 ; i < count ; i++) {
			TreeNode child = tn.getChildAt(i);
			if(child instanceof AspectTreeNode) {
				AspectTreeNode atn = (AspectTreeNode) child;
				if(name.equals(atn.getAspect().getDirective()))
					return false;
			}
		}
		
		return true;
	}
	
	/**
	 * 
	 * @param path
	 * @param onChild if child's of catalog are allowed
	 * @return
	 */
	private boolean canExport(TreePath path, boolean onChild) {
		if(path == null)
			return false;
		if(!onChild)
			return path.getPathCount() == 2 ? true : false;
		return path.getPathCount() >= 2 ? true : false;
	}
	
	private boolean canInsertCategory(TreePath path, String category) {
		if(path == null || path.getPathCount() <= 0)
			return false;
		
		Object node = path.getLastPathComponent();

		if(node instanceof CatalogTreeNode) {
			if(category == null)
				return true;
			CatalogTreeNode ctn = (CatalogTreeNode)node;
			
			Enumeration<?> e = ctn.children();
			Object cNode;
			String cNodeName;
			while(e.hasMoreElements()) {
				cNode = e.nextElement();
				if(cNode instanceof CategoryTreeNode) {
					cNodeName = ((CategoryTreeNode) cNode).getName();
					if(category.equals(cNodeName))
						return false;
				}
			}
			
			return true;
		} else if(node instanceof AspectsTreeNode) {
			if(category == null)
				return true;
			CatalogTreeNode ctn = (CatalogTreeNode)node;
			
			Enumeration<?> e = ctn.children();
			Object cNode;
			String cNodeName;
			while(e.hasMoreElements()) {
				cNode = e.nextElement();
				if(cNode instanceof CategoryTreeNode) {
					cNodeName = ((CategoryTreeNode) cNode).getName();
					if(category.equals(cNodeName))
						return false;
				}
			}
			
			return true;
		} else if(category == null && (node instanceof CategoryTreeNode 
				|| node instanceof AspectTreeNode 
				|| node instanceof CatalogTreeNode)) {
			return true;
		}
		
		return false;
	}
	
	private boolean canInsertCatalog(TreePath path, String name) {
		if(path == null || path.getPathCount() <= 0)
			return name == null ? true : false;
		
		Object node = path.getLastPathComponent();
		
		if(node instanceof CatalogRootTreeNode) {
			if(name == null)
				return true;
			CatalogRootTreeNode crtn = (CatalogRootTreeNode)node;
			
			Enumeration<?> e = crtn.children();
			Object cNode;
			String cNodeName;
			while(e.hasMoreElements()) {
				cNode = e.nextElement();
				if(cNode instanceof CatalogTreeNode) {
					cNodeName = ((CatalogTreeNode) cNode).getName();
					if(name.equals(cNodeName))
						return false;
				}
			}
			
			return true;
		} else if(name == null && (node instanceof CategoryTreeNode 
				|| node instanceof AspectTreeNode 
				|| node instanceof CatalogTreeNode
				|| node instanceof CatalogRootTreeNode)) {
			return true;
		}
		
		return false;
	}
	
	private Icon getResourceIcon(String name) {
		return new ImageIcon(getClass().getResource("images/" + name));
	}
	
	private static String getLocalizedString(String key) {
		return Messages.getString("PearReview.Moderator.manager." + key);
	}
	
	/**
	 * Gets category path of catalog path, if category already exists 
	 * new will be created.
	 * @param catalogPath
	 * @param category
	 * @return category node
	 */
	private TreePath getCatalogCategory(TreePath catalogPath, String category){
		if(catalogPath.getPathCount() < 2)
			return null;
		TreeNode node = (TreeNode)catalogPath.getPathComponent(1);
		
		TreePath path = new TreePath(treeModel.getRoot());
		path = path.pathByAddingChild(node);
		
		Enumeration<?> children = node.children();
		while(children.hasMoreElements()) {
			TreeNode child = (TreeNode)children.nextElement();
			if(child instanceof CategoryTreeNode) {
				if(((CategoryTreeNode)child).getName()
						.equals(category)) {
					return path.pathByAddingChild(child);
				}
			}
		}
		
		return treeModel.addCategoryNode(path, category);
	}
	
	private String getCatalogFreeName(TreePath path, String name) {
		
		String catalogNameIter;
		if(!canInsertCatalog(path, null))
			return null;
		
		short j = 1;
		catalogNameIter = name;
		while(!canInsertCatalog(path, catalogNameIter)) {
			catalogNameIter = name + " " + j;
			j++;
			if(j == Short.MAX_VALUE) {
				Random rand = new Random();
				catalogNameIter = name + " " +-rand.nextInt(Integer.MAX_VALUE);
				break;
			}
		}
		
		return catalogNameIter;
	}
	
	private String getNextNewAspect(TreePath path, String name) {
		String aspectNameIter;
		if(!canInsertAspect(path, null))
			return null;
		
		short j = 1;
		aspectNameIter = name;
		while(!canInsertAspect(path, aspectNameIter)) {
			aspectNameIter = name + " " + j;
			j++;
			if(j == Short.MAX_VALUE) {
				Random rand = new Random();
				aspectNameIter = name + " " + -rand.nextInt(Integer.MAX_VALUE);
				break;
			}
		}
		
		return aspectNameIter;
	}
	
	/**
	 * Action for adding a new catalog to catalog-root
	 *
	 */
	private class AddCatalogAction extends AbstractAction {
		private static final long serialVersionUID = -9060365000109160868L;
		public AddCatalogAction() {
			putValue(AbstractAction.NAME, getLocalizedString("add.catalog"));
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			TreePath[] selectedPaths = tree.getSelectionPaths();
			if(selectedPaths == null) {
				selectedPaths = new TreePath[1];
				selectedPaths[0] = new TreePath(treeModel.getRoot());
			}
			
			TreePath[] addedPaths = new TreePath[selectedPaths.length];
			
			String catalogName = getLocalizedString("add.catalog");
			String catalogDescription = getLocalizedString("new.description");
			String aspectName = getLocalizedString("add.aspect");
			String aspectDescription = getLocalizedString("new.description");
			
			for(int i = 0 ; i < selectedPaths.length ; i++) {
				if(!canInsertCatalog(selectedPaths[i], null))
					return;
				
				int index = -1;
				
				Object node = selectedPaths[i].getLastPathComponent();
				if(node instanceof AspectTreeNode) {
					selectedPaths[i] = selectedPaths[i].getParentPath();
					TreeNode tn = (TreeNode)node;
					index = tn.getParent().getIndex(tn);
				}
				
				node = selectedPaths[i].getLastPathComponent();
				if(node instanceof CategoryTreeNode) {
					selectedPaths[i] = selectedPaths[i].getParentPath();
					TreeNode tn = (TreeNode)node;
					index = tn.getParent().getIndex(tn) + 1;
				}
				
				node = selectedPaths[i].getLastPathComponent();
				if(node instanceof CatalogTreeNode) {
					selectedPaths[i] = selectedPaths[i].getParentPath();
					TreeNode tn = (TreeNode)node;
					index = tn.getParent().getIndex(tn) + 1;
				}
				
				addedPaths[i] = treeModel.insertCatalogNode(
						selectedPaths[i],
						getCatalogFreeName(selectedPaths[i], catalogName), 
						catalogDescription,
						index);
				
				tree.expandPath(addedPaths[i]);
				
				/* and add aspect */
				treeModel.addAspectNode(addedPaths[i], 
						getNextNewAspect(addedPaths[i], aspectName), 
						aspectDescription);
			}
			
			tree.setSelectionPaths(addedPaths);
		}
	}
	
	/**
	 * Action for adding a new category to catalog/aspect-list
	 *
	 */
	private class AddCategoryAction extends AbstractAction {
		private static final long serialVersionUID = 1882404362675390321L;
		public AddCategoryAction() {
			setEnabled(false);
			
			Icon largeIcon = getResourceIcon("folder-new.png");
			Icon smallIcon = getResourceIcon("folder-new_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(AbstractAction.NAME, getLocalizedString("add.category"));
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			final TreePath[] selectedPaths = tree.getSelectionPaths();
			
			if(selectedPaths == null)
				return;
			
			final TreePath[] addedPaths = new TreePath[selectedPaths.length];
			
			String categoryName = getLocalizedString("add.category");
			String aspectName = getLocalizedString("add.aspect");
			String aspectDescription = getLocalizedString("new.description");
			
			String categoryNameIter;
			
			for(int i = 0 ; i < selectedPaths.length ; i++) {
				if(!canInsertCategory(selectedPaths[i], null))
					return;
				
				int index = -1;
				
				Object node = selectedPaths[i].getLastPathComponent();
				if(node instanceof AspectTreeNode) {
					selectedPaths[i] = selectedPaths[i].getParentPath();
					TreeNode tn = (TreeNode)node;
					index = tn.getParent().getIndex(tn);
				}
				
				node = selectedPaths[i].getLastPathComponent();
				if(node instanceof CategoryTreeNode) {
					selectedPaths[i] = selectedPaths[i].getParentPath();
					TreeNode tn = (TreeNode)node;
					index = tn.getParent().getIndex(tn) + 1;
				}
				
				int j = 1;
				categoryNameIter = categoryName;
				while(!canInsertCategory(selectedPaths[i], categoryNameIter)) {
					categoryNameIter = categoryName + " " + j;
					j++;
					if(j == Short.MAX_VALUE) {
						Random rand = new Random();
						categoryNameIter = categoryName + " " +
								-rand.nextInt(Integer.MAX_VALUE);
						break;
					}
				}
				
				addedPaths[i] = treeModel.insertCategoryNode(
						selectedPaths[i],
						categoryNameIter,
						index);
				/* and adds aspects */
				treeModel.addAspectNode(addedPaths[i], 
						getNextNewAspect(addedPaths[i], aspectName), 
						aspectDescription);
				
				tree.expandPath(addedPaths[i]);
			}
			
			tree.setSelectionPaths(addedPaths);
		}
	}

	/**
	 * Action for adding a new aspect to category/catalog/aspect-list
	 *
	 */
	private class AddAspectAction extends AbstractAction {
		private static final long serialVersionUID = 3792034004790481840L;
		public AddAspectAction() {
			setEnabled(false);
			
			Icon largeIcon = getResourceIcon("document-new.png");
			Icon smallIcon = getResourceIcon("document-new_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(AbstractAction.NAME, getLocalizedString("add.aspect"));
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			final TreePath[] selectedPaths = tree.getSelectionPaths();
			
			if(selectedPaths == null)
				return;
			
			final TreePath[] addedPaths = new TreePath[selectedPaths.length];
			
			String aspectName = getLocalizedString("add.aspect");
			String aspectDescription = getLocalizedString("new.description");
			
			for(int i = 0 ; i < selectedPaths.length ; i++) {
				if(!canInsertAspect(selectedPaths[i], null))
					return;
				
				int index = -1;
				Object node = selectedPaths[i].getLastPathComponent();
				
				if(node instanceof AspectTreeNode) {
					selectedPaths[i] = selectedPaths[i].getParentPath();
					TreeNode tn = (TreeNode)node;
					index = tn.getParent().getIndex(tn) + 1;
				}
					
				addedPaths[i] = treeModel.insertAspectNode(
						selectedPaths[i], 
						getNextNewAspect(selectedPaths[i], aspectName), 
						aspectDescription,
						index);
			}
			
			tree.setSelectionPaths(addedPaths);
		}
	}
	
	/**
	 * Action for delete aspects/categories/catalogs/aspect-lists
	 *
	 */
	private class DeleteAction extends AbstractAction {
		private static final long serialVersionUID = 5686085243839917076L;
		public DeleteAction() {
			setEnabled(false);
			
			Icon largeIcon = getResourceIcon("edit-delete_22.png");
			Icon smallIcon = getResourceIcon("edit-delete_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(AbstractAction.NAME, getLocalizedString("remove"));
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			TreePath[] selectedPaths = tree.getSelectionPaths();
			
			if(selectedPaths == null)
				return;
			
			selectedPaths = CatalogTreeModel.removeLowerNodes(selectedPaths);
			
			if(selectedPaths == null)
				return;
			
			for(TreePath cPath : selectedPaths) {
				removeNode(cPath);
			}
		}
	}
	
	private void removeNode(TreePath path) {
		Object node = path.getLastPathComponent();
		TreeNode parent = ((TreeNode)node).getParent();
		
		treeModel.removeNode(path);
		
		while(parent != null && parent.getChildCount() <= 0) {
			parent = parent.getParent();
			path = path.getParentPath();
			treeModel.removeNode(path);
		}
	}
	
	private class SelectionListener implements TreeSelectionListener, 
			Serializable {
		private static final long serialVersionUID = -8387974386333101336L;

		@Override
		public void valueChanged(TreeSelectionEvent e) {
			TreePath nodePath = tree.getSelectionPath();
			
			deleteAction.setEnabled(canRemove(nodePath));
			addCatalogAction.setEnabled(canInsertCatalog(nodePath, null));
			addCategoryAction.setEnabled(canInsertCategory(nodePath, null));
			addAspectAction.setEnabled(canInsertAspect(nodePath, null));
			
			exportAction.setEnabled(canExport(nodePath, true));
			
			if(tree.getSelectionPath() == null) {
				operationPanel.showNoForm();
				return;
			}
			
			Object node = nodePath.getLastPathComponent();
			
			if (node instanceof AspectTreeNode) {
				
				AspectTreeNode selectedNode = (AspectTreeNode)node;
				Aspect aspect = selectedNode.getAspect();
				
				TreeNode parentNode = selectedNode.getParent();
				if(parentNode instanceof CategoryTreeNode) {
					CategoryTreeNode ctn = (CategoryTreeNode)parentNode;
					aspect.setCategory(ctn.getName());
					parentNode = parentNode.getParent();
				} else if(parentNode instanceof AspectsTreeNode) {
					aspect.setCategory("");
				}
				
				if(CatalogManager.this.canRemove(nodePath)) {
					List<String> categoryList = new ArrayList<String>();
					int childCount = parentNode.getChildCount();
					for(int i = 0 ; i < childCount ; i++) {
						TreeNode tn = parentNode.getChildAt(i);
						if(!(tn instanceof CategoryTreeNode))
							continue;
						categoryList.add(((CategoryTreeNode)tn).getName());
					}
					
					String[] categories = new String[categoryList.size() + 1];
					categoryList.toArray(categories);
					categories[categories.length - 1] = "";
					
					operationPanel.showAspectForm(aspect,
						categories);
				} else {
					operationPanel.showAspectForm(aspect);
				}
				
			} else if (node instanceof CategoryTreeNode) {
				CategoryTreeNode selectedCategory = (CategoryTreeNode)node;
				operationPanel.showCategoryForm(selectedCategory.getName());
			} else if(node instanceof CatalogTreeNode) {
				CatalogTreeNode ctn = (CatalogTreeNode)node;
				operationPanel.showCatalogForm(ctn.getName(), 
						ctn.getCatalog().getDescription());
			} else {
				operationPanel.showNoForm();
			}
		}
	}
	
	private class PopupMouseAdapter extends MouseAdapter 
			implements Serializable {
		private static final long serialVersionUID = -211946597663871629L;

		private void popup(MouseEvent e) {
			popupMenu.removeAll();
			int x = e.getX();
			int y = e.getY();
			TreePath clickedPath = tree.getPathForLocation(x, y);
			TreePath[] selectedPaths = tree.getSelectionPaths();
			
			if(clickedPath == null)
				return;
			
			if(selectedPaths != null 
					&& Arrays.asList(selectedPaths).contains(clickedPath) 
					&& selectedPaths.length > 1) {
					
			} else {
				tree.setSelectionPath(clickedPath);
				
				if(canInsertAspect(clickedPath, null))
					popupMenu.add(getAddAspectItem());
				
				if(canInsertCategory(clickedPath, null))
					popupMenu.add(getAddCategoryItem());
				
				if(canInsertCatalog(clickedPath, null))
					popupMenu.add(getAddCatalogItem());

				if(canRemove(clickedPath))
					popupMenu.add(getRemoveItem());
				
				if(canExport(clickedPath, false)) {
					if(popupMenu.getComponentCount() > 0)
						popupMenu.addSeparator();
					popupMenu.add(getExportItem());
				}
				
				if(clickedPath.getLastPathComponent() 
						instanceof CatalogRootTreeNode) {
					if(popupMenu.getComponentCount() > 0)
						popupMenu.addSeparator();
					popupMenu.add(getImportItem());
				}
			}
			if(popupMenu.getComponentCount() > 0)
				popupMenu.show(tree, x, y);
		}
		
		@Override
		public void mouseReleased(MouseEvent e) {
			if (!e.isPopupTrigger())
				return;
			
			popup(e);
		}
		
		@Override
		public void mousePressed(MouseEvent e) {
			if (!e.isPopupTrigger())
				return;
			
			popup(e);
		}
		
		private JMenuItem getAddCatalogItem() {
			JMenuItem menuItem = new JMenuItem();
			menuItem.setAction(addCatalogAction);
			return menuItem;
		}
		
		private JMenuItem getAddCategoryItem() {
			JMenuItem menuItem = new JMenuItem();
			menuItem.setAction(addCategoryAction);
			return menuItem;
		}
		
		private JMenuItem getAddAspectItem() {
			JMenuItem menuItem = new JMenuItem();
			menuItem.setAction(addAspectAction);
			return menuItem;
		}
		
		private JMenuItem getRemoveItem() {
			JMenuItem menuItem = new JMenuItem();
			menuItem.setAction(deleteAction);
			return menuItem;
		}
		
		private JMenuItem getExportItem() {
			JMenuItem menuItem = new JMenuItem();
			menuItem.setAction(exportAction);
			return menuItem;
		}
		
		private JMenuItem getImportItem() {
			JMenuItem menuItem = new JMenuItem();
			menuItem.setAction(importAction);
			return menuItem;
		}
	}
	
	private class SaveAction extends AbstractAction {
		private static final long serialVersionUID = -862186863653837263L;
		public SaveAction() {
			putValue(AbstractAction.NAME, 
					getLocalizedString("save"));
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			TreePath selectedPath = tree.getSelectionPath();
			Object node = selectedPath.getLastPathComponent();
			
			if(node instanceof AspectTreeNode) {
				AspectTreeNode atn = (AspectTreeNode)node;
				Aspect newAspect = operationPanel.getAspect();
				Aspect aspect = atn.getAspect();
				
				if(newAspect.getDirective().equals("")) {
					newAspect.setDirective(aspect.getDirective());
					operationPanel.setAspect(newAspect);
				}
				
				String oldCategory = aspect.getCategory();
				
				aspect.setDirective(newAspect.getDirective());
				aspect.setDescription(newAspect.getDescription());
				aspect.setCategory(newAspect.getCategory());
				
				if(aspect.getCategory().equals(oldCategory)) {
					treeModel.editAspectNode(selectedPath, 
						aspect.getDirective(), 
						aspect.getDescription());
				} else {
					Object ctn = selectedPath.getPathComponent(1);
					
					TreePath addPath = new TreePath(treeModel.getRoot());
					addPath = addPath.pathByAddingChild(ctn);
					
					/* remove */
					treeModel.removeNode(selectedPath);
					
					/* add again */
					if(aspect.getCategory() != null 
							&& !aspect.getCategory().equals(""))						
						addPath = getCatalogCategory(selectedPath, 
								aspect.getCategory());
					TreePath aspectPath = treeModel.addAspectNode(
							addPath, aspect);
					
					/* select again */
					tree.setSelectionPath(aspectPath);
				}
				
			} else if(node instanceof CategoryTreeNode) {
				CategoryTreeNode ctn = (CategoryTreeNode)node;
				String oldCategory = ctn.getName();
				String newCategory = operationPanel.getCategory();
				
				if(newCategory == null || newCategory.equals("")) {
					operationPanel.setCategory(oldCategory);
					return;
				}
				
				if(oldCategory.equals(newCategory))
					return;
				
				TreeNode parentNode = ctn.getParent();
				int childCount = parentNode.getChildCount();
				for(int i = 0 ; i < childCount ; i++) {
					TreeNode tn = parentNode.getChildAt(i);
					if(!(tn instanceof CategoryTreeNode))
						continue;
					
					CategoryTreeNode ctnOther = (CategoryTreeNode)tn;
					
					if(ctnOther.getName().equals(newCategory)) {
						String errorMessage = 
							getLocalizedString("error.category_exists");
						Formatter formatter = new Formatter();
						formatter.format(errorMessage, oldCategory);
						errorMessage = formatter.toString();
						
						JOptionPane.showMessageDialog(CatalogManager.this, 
								errorMessage,
								getLocalizedString("error"),
								JOptionPane.ERROR_MESSAGE);
						return;
					}
				}
				
				treeModel.editCategoryNode(selectedPath, newCategory);
			} else if(node instanceof CatalogTreeNode) {
				CatalogTreeNode ctn = (CatalogTreeNode)node;
				String oldName = ctn.getName();
				String newName = operationPanel.getCatalogName();
				String newDescription = operationPanel.getCatalogDescription();
				
				if(newName == null || newName.equals("") 
						|| newName.equals(oldName)) {
					newName = oldName;
					treeModel.editCatalogNode(selectedPath, 
							newName, 
							newDescription);
					operationPanel.setCatalog(newName, newDescription);
					return;
				}
				
				TreeNode parentNode = ctn.getParent();
				int childCount = parentNode.getChildCount();
				for(int i = 0 ; i < childCount ; i++) {
					TreeNode tn = parentNode.getChildAt(i);
					if(!(tn instanceof CatalogTreeNode))
						continue;
					
					CatalogTreeNode ctnOther = (CatalogTreeNode)tn;
					
					if(ctnOther.getName().equals(newName)) {
						String errorMessage = 
							getLocalizedString("error.catalog_exists");
						Formatter formatter = new Formatter();
						formatter.format(errorMessage, oldName);
						errorMessage = formatter.toString();
						
						JOptionPane.showMessageDialog(CatalogManager.this, 
								errorMessage,
								getLocalizedString("error"),
								JOptionPane.ERROR_MESSAGE);
						return;
					}
				}
				treeModel.editCatalogNode(selectedPath, 
						newName, 
						newDescription);
			}
		}
	}
	
	private class TreeDropTargetAdapter extends DropTargetAdapter
			implements Serializable {
		private static final long serialVersionUID = -5698056486123161710L;

		@Override
		public void drop(DropTargetDropEvent dtde) {
			Point p = dtde.getLocation();
			TreePath targetPath = tree.getClosestPathForLocation(p.x, p.y);
			TreePath sourcePath = tree.getSelectionPath();
			
			if(treeModel.canMoveNode(sourcePath, targetPath)) {
				
				/* save expanded nodes */
				boolean sourceExpanded = tree.isExpanded(sourcePath);
				TreeNode sourceNode = 
						(TreeNode)sourcePath.getLastPathComponent();
				boolean[] sourceChildrenExpanded = 
						new boolean[sourceNode.getChildCount()];
				for(int i = 0 ; i < sourceChildrenExpanded.length ; i++) {
					TreeNode tn = sourceNode.getChildAt(i);
					sourceChildrenExpanded[i] = 
							tree.isExpanded(sourcePath.pathByAddingChild(tn));
				}
				
				boolean targetExpanded = tree.isExpanded(targetPath);
				TreeNode targetNode = 
						(TreeNode)targetPath.getLastPathComponent();
				boolean[] targetChildrenExpanded = 
						new boolean[targetNode.getChildCount()];
				for(int i = 0 ; i < targetChildrenExpanded.length ; i++) {
					TreeNode tn = targetNode.getChildAt(i);
					targetChildrenExpanded[i] = 
							tree.isExpanded(targetPath.pathByAddingChild(tn));
				}
				
				/* move */
				treeModel.moveNode(sourcePath, targetPath);
				tree.setSelectionPath(sourcePath);
				
				/* expand children */
				if(sourceExpanded)
					tree.expandPath(sourcePath);
				if(targetExpanded)
					tree.expandPath(targetPath);
				
				for(int i = 0 ; i < sourceChildrenExpanded.length ; i++) {
					if(!sourceChildrenExpanded[i])
						return;
					TreeNode tn = sourceNode.getChildAt(i);
					tree.expandPath(sourcePath.pathByAddingChild(tn));
				}
				
				for(int i = 0 ; i < targetChildrenExpanded.length ; i++) {
					if(!targetChildrenExpanded[i])
						return;
					TreeNode tn = targetNode.getChildAt(i);
					tree.expandPath(targetPath.pathByAddingChild(tn));
				}
			} else if(canMove(targetPath, sourcePath)) {
				TreeNode sourceNode = 
						(TreeNode)sourcePath.getLastPathComponent();
				
				TreePath selectPath = null;
				
				/* insert catalog */
				if(sourceNode instanceof CatalogTreeNode) {
					CatalogTreeNode ctn = (CatalogTreeNode)sourceNode;
					selectPath = treeModel.addCatalogNode(targetPath,
							ctn.getCatalog(), ctn.getName());
				/* insert category */
				} else if(sourceNode instanceof CategoryTreeNode) {
					boolean expanded = tree.isExpanded(sourcePath);
					CategoryTreeNode ctn = (CategoryTreeNode)sourceNode;
					CatalogTreeNode catalogNode = 
							(CatalogTreeNode)targetPath.getLastPathComponent();
					
					/* get aspects before remove node! */
					Enumeration<?> e = ctn.children();
					
					/* remove */
					treeModel.removeNode(sourcePath);
					
					/* create category node if not already exists */
					Enumeration<?> catalogChildren = 
							catalogNode.children();
					while(catalogChildren.hasMoreElements()) {
						TreeNode child = (TreeNode)catalogChildren
								.nextElement();
						if(child instanceof CategoryTreeNode) {
							if(((CategoryTreeNode)child).getName()
									.equals(ctn.getName())) {
								selectPath = targetPath
										.pathByAddingChild(child);
								expanded = expanded 
										|| tree.isExpanded(selectPath);
								break;
							}
						}
					}
					if(selectPath == null) {
						selectPath = treeModel
								.addCategoryNode(targetPath, ctn.getName());
					}
					
					if(expanded)
						tree.expandPath(selectPath);
					
					/* adds aspects */
					while(e.hasMoreElements()) {
						treeModel.addAspectNode(selectPath, 
								((AspectTreeNode)e.nextElement()).getAspect());
					}
				/* insert aspect */
				} else if(sourceNode instanceof AspectTreeNode) {
					Object targetNode = targetPath.getLastPathComponent();
					int index = -1;
					if(targetNode instanceof AspectTreeNode) {
						TreeNode tn = (TreeNode) targetNode;
						index  = tn.getParent().getIndex(tn);
						targetPath = targetPath.getParentPath();
					}
					
					if(targetPath.equals(sourcePath.getParentPath()))
						treeModel.removeNode(sourcePath);
					else
						removeNode(sourcePath);
					AspectTreeNode atn = (AspectTreeNode)sourceNode;
					selectPath = treeModel.insertAspectNode(targetPath, 
							atn.getAspect(),
							index);
				}
				
				/* select source */
				tree.setSelectionPath(selectPath);
				
			} else {
				dtde.rejectDrop();
				dtde.dropComplete(false);
			}
		}
	}
	
	private class ExportAction extends AbstractAction {
		private static final long serialVersionUID = -4562301609446184479L;
		
		public ExportAction() {
			setEnabled(false);
			
			putValue(Action.NAME, getLocalizedString("export"));
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			TreePath selectedPath = tree.getSelectionPath();
			
			if(!canExport(selectedPath, true))
				return;
			
			TreeNode node = (TreeNode)selectedPath.getPathComponent(1);
			int index = treeModel.getRoot().getIndex(node);
			Catalog catalog = treeModel.getCatalog(index);
			String name = treeModel.getCatalogName(index);
			
			XMLFileFilter xmlFilter = new XMLFileFilter();
			
			final JFileChooser saveDialog = new JFileChooser();
			saveDialog.setMultiSelectionEnabled(false);
			saveDialog.setFileFilter(xmlFilter);
			saveDialog.setAcceptAllFileFilterUsed(false);
			saveDialog.setSelectedFile(new File(name));
			
			int ret = saveDialog.showSaveDialog(CatalogManager.this);
				 
			if (JFileChooser.APPROVE_OPTION != ret)
				return;
			
			File selectedFile = saveDialog.getSelectedFile();
			if(!xmlFilter.accept(selectedFile)) {
				String fileName = selectedFile.getName();
				fileName += XMLFileFilter.EXTENSION;
				selectedFile = new File(selectedFile.getParent(), fileName);
			}
			
			try {
				CatalogWriter writer = new XMLCatalogWriter();
				writer.write(catalog, selectedFile);
			} catch (Throwable th) {
				// TODO error handling
				th.printStackTrace();
			}
		}
	}
	
	private class ImportAction extends AbstractAction {
		private static final long serialVersionUID = 1580451683945278197L;
		public ImportAction() {
			putValue(Action.NAME, getLocalizedString("import"));
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			XMLFileFilter xmlFilter = new XMLFileFilter();
			
			final JFileChooser openDialog = new JFileChooser();
			openDialog.setMultiSelectionEnabled(false);
			openDialog.setFileFilter(xmlFilter);
			openDialog.setAcceptAllFileFilterUsed(false);
			
			int ret = openDialog.showOpenDialog(CatalogManager.this);
			 
			if (JFileChooser.APPROVE_OPTION != ret)
				return;
			
			File selectedFile = openDialog.getSelectedFile();
			
			CatalogReader reader = new XMLCatalogReader();
			
			try {
				Catalog catalog = reader.read(selectedFile);
				
				String name = getCatalogName(selectedFile);
				
				TreePath rootPath = new TreePath(treeModel.getRoot());
				
				treeModel.addCatalogNode(rootPath, catalog, 
						getCatalogFreeName(rootPath, name));
			} catch (Throwable th) {
				// TODO error handling
				th.printStackTrace();
			}
		}
	}
	
	public static void showManagerDialog(Component comp, File workspace) {
		String title = CatalogManager.getLocalizedString("title");
		JDialog dialog;
		
		Component rootComp = SwingUtilities.getRoot(comp);
		if(rootComp instanceof Frame) {
			dialog = new JDialog((Frame)rootComp, title, true);
		} else if (rootComp instanceof Dialog) {
			dialog = new JDialog((Dialog)rootComp, title, true);
		} else {
			dialog = new JDialog((Frame)null, title, true);
		}
		
		dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
		//dialog.setAlwaysOnTop(alwaysOnTop);
		
		final CatalogManager cm = 
				new CatalogManager(workspace);
		
		dialog.add(cm);
		dialog.addWindowListener(new WindowListener() {
			@Override
			public void windowActivated(WindowEvent e) {}
			@Override
			public void windowClosed(WindowEvent e) {}
			@Override
			public void windowClosing(WindowEvent e) {
				try {
					cm.saveCatalogs();
				} catch (Throwable e1) {
					// TODO error message?!
					e1.printStackTrace();
				}
				e.getWindow().setVisible(false);
			}
			@Override
			public void windowDeactivated(WindowEvent e) {}
				@Override
			public void windowDeiconified(WindowEvent e) {}
			@Override
			public void windowIconified(WindowEvent e) {}
			@Override
			public void windowOpened(WindowEvent e) {}
		});
		
		/* set dialog size */
		dialog.pack();
		dialog.setMinimumSize(dialog.getSize());
		Dimension dialogSize = new Dimension(720, 560);
		dialog.setSize(dialogSize);
		
		dialog.setLocationRelativeTo(dialog.getOwner());
		
		dialog.validate();
		dialog.setVisible(true);
	}
	
	public static void main(String[] args) {
		File f = new File(CATALOG_DIRECTORY + "/");
		if(args.length > 0) {
			f = new File(args[0]);
		}
		
		showManagerDialog(null, f);
	}
}
